"""Custom form that supports groupings."""

from django.forms import Form, Field
from django.forms.forms import BoundField, DeclarativeFieldsMetaclass
from django.forms.util import flatatt
from django.utils.datastructures import SortedDict
from django.utils.html import conditional_escape
from django.utils.encoding import force_unicode
from django.utils.safestring import mark_safe

class MetaSection(type):

    creation_counter = 0

    def __new__(cls, name, bases, dct):
        fields = [(field_name, field) for field_name, field in dct.items() if isinstance(field, Field)]
        fields.sort(key=lambda x:x[1].creation_counter)
        dct['fields'] = SortedDict(fields)

        new_class = type.__new__(cls, name, bases, dct)
        new_class.creation_counter = MetaSection.creation_counter
        MetaSection.creation_counter += 1

        return new_class

class FormSection(object):

    """XXX"""

    __metaclass__ = MetaSection

    label = None

class GroupedMeta(DeclarativeFieldsMetaclass):

    def __new__(cls, name, bases, attrs):
        sections = [value for value in attrs.values() if \
            isinstance(value, type) and issubclass(value, FormSection)]
        sections.sort(key=lambda x:x.creation_counter)
        attrs['sections'] = sections

        fields = {}
        for section in sections:
            fields.update(section.fields)
        attrs.update(fields)

        new_class = super(GroupedMeta, cls).__new__(cls, name, bases, attrs)
        return new_class

class GroupedForm(Form):

    __metaclass__ = GroupedMeta

    def __init__(self, *args, **kwargs):
        self.group_attrs = kwargs.pop('group_attrs', {})
        self.section_header_attrs = kwargs.pop('section_header_attrs', {})
        self.label_attrs = kwargs.pop('label_attrs', {})
        self.filter_fields = kwargs.pop('filter_fields', ())
        super(Form, self).__init__(*args, **kwargs)
        for field in self.filter_fields:
            del self.fields[field]

    def _html_output(self, normal_row, error_row, row_ender, help_text_html,
                     errors_on_separate_row, section_start, section_end, section_label):
        top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
        output, hidden_fields = [], []
        for section in self.sections:
            output.append(section_start)
            if section.label:
                output.append(section_label % {'label': section.label,
                                               'attrs': flatatt(self.section_header_attrs)})

            for name, field in section.fields.items():
                if name in self.filter_fields:
                    continue
                bf = BoundField(self, field, name)
                bf_errors = self.error_class([conditional_escape(error) for error in bf.errors]) # Escape and cache in local variable.
                if bf.is_hidden:
                    if bf_errors:
                        top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors])
                    hidden_fields.append(unicode(bf))
                else:
                    if errors_on_separate_row and bf_errors:
                        output.append(error_row % force_unicode(bf_errors))
                    if bf.label:
                        label = conditional_escape(force_unicode(bf.label))
                        # Only add the suffix if the label does not end in
                        # punctuation.
                        if self.label_suffix:
                            if label[-1] not in ':?.!':
                                label += self.label_suffix
                        label = bf.label_tag(label) or ''
                    else:
                        label = ''
                    if field.help_text:
                        help_text = help_text_html % force_unicode(field.help_text)
                    else:
                        help_text = u''
                    output.append(normal_row % {'errors': force_unicode(bf_errors),
                                                'label': force_unicode(label),
                                                'field': unicode(bf),
                                                'help_text': help_text,
                                                'label_attrs': flatatt(self.label_attrs),
                                               })
            output.append(section_end)

        if top_errors:
            output.insert(0, error_row % force_unicode(top_errors))
        if hidden_fields: # Insert any hidden fields in the last row.
            str_hidden = u''.join(hidden_fields)
            if output:
                last_row = output[-1]
                # Chop off the trailing row_ender (e.g. '</td></tr>') and
                # insert the hidden fields.
                if not last_row.endswith(row_ender):
                    # This can happen in the as_p() case (and possibly others
                    # that users write): if there are only top errors, we may
                    # not be able to conscript the last row for our purposes,
                    # so insert a new, empty row.
                    last_row = normal_row % {'errors': '', 'label': '', 'field': '', 'help_text': ''}
                    output.append(last_row)
                output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
            else:
                # If there aren't any rows in the output, just append the
                # hidden fields.
                output.append(str_hidden)
        return mark_safe(u'\n'.join(output))

    def as_table(self):
        return self._html_output(
            u'<tr><th%(label_attrs)s>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>',
            u'<tr><td colspan="2">%s</td></tr>',
            u'</td></tr>',
            u'<br />%s',
            False,
            u'<table%s>' % (flatatt(self.group_attrs),),
            u'</table>',
            u'<tr><th colspan=2%(attrs)s>%(label)s</th></tr>')
